/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

/*****************************************************************************
 * name:		be_ai_chat.c
 *
 * desc:		bot chat AI
 *
 * $Archive: /code/botlib/be_ai_chat.c $
 *
 *****************************************************************************/

#include "../game/q_shared.h"
#include "l_memory.h"
#include "l_libvar.h"
#include "l_script.h"
#include "l_precomp.h"
#include "l_struct.h"
#include "l_utils.h"
#include "l_log.h"
#include "aasfile.h"
#include "../game/botlib.h"
#include "../game/be_aas.h"
#include "be_aas_funcs.h"
#include "be_interface.h"
#include "../game/be_ea.h"
#include "../game/be_ai_chat.h"


//escape character
#define ESCAPE_CHAR				0x01	//'_'
//
// "hi ", people, " ", 0, " entered the game"
//becomes:
// "hi _rpeople_ _v0_ entered the game"
//

//match piece types
#define MT_VARIABLE					1		//variable match piece
#define MT_STRING					2		//string match piece
//reply chat key flags
#define RCKFL_AND					1		//key must be present
#define RCKFL_NOT					2		//key must be absent
#define RCKFL_NAME					4		//name of bot must be present
#define RCKFL_STRING				8		//key is a string
#define RCKFL_VARIABLES				16		//key is a match template
#define RCKFL_BOTNAMES				32		//key is a series of botnames
#define RCKFL_GENDERFEMALE			64		//bot must be female
#define RCKFL_GENDERMALE			128		//bot must be male
#define RCKFL_GENDERLESS			256		//bot must be genderless
//time to ignore a chat message after using it
#define CHATMESSAGE_RECENTTIME	20

//the actuall chat messages
typedef struct bot_chatmessage_s
{
    char *chatmessage;					//chat message string
    float time;							//last time used
    struct bot_chatmessage_s *next;		//next chat message in a list
} bot_chatmessage_t;
//bot chat type with chat lines
typedef struct bot_chattype_s
{
    char name[MAX_CHATTYPE_NAME];
    int numchatmessages;
    bot_chatmessage_t *firstchatmessage;
    struct bot_chattype_s *next;
} bot_chattype_t;
//bot chat lines
typedef struct bot_chat_s
{
    bot_chattype_t *types;
} bot_chat_t;

//random string
typedef struct bot_randomstring_s
{
    char *string;
    struct bot_randomstring_s *next;
} bot_randomstring_t;
//list with random strings
typedef struct bot_randomlist_s
{
    char *string;
    int numstrings;
    bot_randomstring_t *firstrandomstring;
    struct bot_randomlist_s *next;
} bot_randomlist_t;

//synonym
typedef struct bot_synonym_s
{
    char *string;
    float weight;
    struct bot_synonym_s *next;
} bot_synonym_t;
//list with synonyms
typedef struct bot_synonymlist_s
{
    unsigned long int context;
    float totalweight;
    bot_synonym_t *firstsynonym;
    struct bot_synonymlist_s *next;
} bot_synonymlist_t;

//fixed match string
typedef struct bot_matchstring_s
{
    char *string;
    struct bot_matchstring_s *next;
} bot_matchstring_t;

//piece of a match template
typedef struct bot_matchpiece_s
{
    int type;
    bot_matchstring_t *firststring;
    int variable;
    struct bot_matchpiece_s *next;
} bot_matchpiece_t;
//match template
typedef struct bot_matchtemplate_s
{
    unsigned long int context;
    int type;
    int subtype;
    bot_matchpiece_t *first;
    struct bot_matchtemplate_s *next;
} bot_matchtemplate_t;

//reply chat key
typedef struct bot_replychatkey_s
{
    int flags;
    char *string;
    bot_matchpiece_t *match;
    struct bot_replychatkey_s *next;
} bot_replychatkey_t;
//reply chat
typedef struct bot_replychat_s
{
    bot_replychatkey_t *keys;
    float priority;
    int numchatmessages;
    bot_chatmessage_t *firstchatmessage;
    struct bot_replychat_s *next;
} bot_replychat_t;

//string list
typedef struct bot_stringlist_s
{
    char *string;
    struct bot_stringlist_s *next;
} bot_stringlist_t;

//chat state of a bot
typedef struct bot_chatstate_s
{
    int gender;											//0=it, 1=female, 2=male
    int client;											//client number
    char name[32];										//name of the bot
    char chatmessage[MAX_MESSAGE_SIZE];
    int handle;
    //the console messages visible to the bot
    bot_consolemessage_t *firstmessage;			//first message is the first typed message
    bot_consolemessage_t *lastmessage;			//last message is the last typed message, bottom of console
    //number of console messages stored in the state
    int numconsolemessages;
    //the bot chat lines
    bot_chat_t *chat;
} bot_chatstate_t;

typedef struct {
    bot_chat_t	*chat;
    char		filename[MAX_QPATH];
    char		chatname[MAX_QPATH];
} bot_ichatdata_t;

bot_ichatdata_t	*ichatdata[MAX_CLIENTS];

bot_chatstate_t *botchatstates[MAX_CLIENTS+1];
//console message heap
bot_consolemessage_t *consolemessageheap = NULL;
bot_consolemessage_t *freeconsolemessages = NULL;
//list with match strings
bot_matchtemplate_t *matchtemplates = NULL;
//list with synonyms
bot_synonymlist_t *synonyms = NULL;
//list with random strings
bot_randomlist_t *randomstrings = NULL;
//reply chats
bot_replychat_t *replychats = NULL;

//========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//========================================================================
bot_chatstate_t *BotChatStateFromHandle(int handle)
{
    if (handle <= 0 || handle > MAX_CLIENTS)
    {
        botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle);
        return NULL;
    } //end if
    if (!botchatstates[handle])
    {
        botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle);
        return NULL;
    } //end if
    return botchatstates[handle];
} //end of the function BotChatStateFromHandle
//===========================================================================
// initialize the heap with unused console messages
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void InitConsoleMessageHeap(void)
{
    int i, max_messages;

    if (consolemessageheap) FreeMemory(consolemessageheap);
    //
    max_messages = (int) LibVarValue("max_messages", "1024");
    consolemessageheap = (bot_consolemessage_t *) GetClearedHunkMemory(max_messages *
                         sizeof(bot_consolemessage_t));
    consolemessageheap[0].prev = NULL;
    consolemessageheap[0].next = &consolemessageheap[1];
    for (i = 1; i < max_messages-1; i++)
    {
        consolemessageheap[i].prev = &consolemessageheap[i - 1];
        consolemessageheap[i].next = &consolemessageheap[i + 1];
    } //end for
    consolemessageheap[max_messages-1].prev = &consolemessageheap[max_messages-2];
    consolemessageheap[max_messages-1].next = NULL;
    //pointer to the free console messages
    freeconsolemessages = consolemessageheap;
} //end of the function InitConsoleMessageHeap
//===========================================================================
// allocate one console message from the heap
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_consolemessage_t *AllocConsoleMessage(void)
{
    bot_consolemessage_t *message;
    message = freeconsolemessages;
    if (freeconsolemessages) freeconsolemessages = freeconsolemessages->next;
    if (freeconsolemessages) freeconsolemessages->prev = NULL;
    return message;
} //end of the function AllocConsoleMessage
//===========================================================================
// deallocate one console message from the heap
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void FreeConsoleMessage(bot_consolemessage_t *message)
{
    if (freeconsolemessages) freeconsolemessages->prev = message;
    message->prev = NULL;
    message->next = freeconsolemessages;
    freeconsolemessages = message;
} //end of the function FreeConsoleMessage
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotRemoveConsoleMessage(int chatstate, int handle)
{
    bot_consolemessage_t *m, *nextm;
    bot_chatstate_t *cs;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return;

    for (m = cs->firstmessage; m; m = nextm)
    {
        nextm = m->next;
        if (m->handle == handle)
        {
            if (m->next) m->next->prev = m->prev;
            else cs->lastmessage = m->prev;
            if (m->prev) m->prev->next = m->next;
            else cs->firstmessage = m->next;

            FreeConsoleMessage(m);
            cs->numconsolemessages--;
            break;
        } //end if
    } //end for
} //end of the function BotRemoveConsoleMessage
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotQueueConsoleMessage(int chatstate, int type, char *message)
{
    bot_consolemessage_t *m;
    bot_chatstate_t *cs;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return;

    m = AllocConsoleMessage();
    if (!m)
    {
        botimport.Print(PRT_ERROR, "empty console message heap\n");
        return;
    } //end if
    cs->handle++;
    if (cs->handle <= 0 || cs->handle > 8192) cs->handle = 1;
    m->handle = cs->handle;
    m->time = AAS_Time();
    m->type = type;
    strncpy(m->message, message, MAX_MESSAGE_SIZE);
    m->next = NULL;
    if (cs->lastmessage)
    {
        cs->lastmessage->next = m;
        m->prev = cs->lastmessage;
        cs->lastmessage = m;
    } //end if
    else
    {
        cs->lastmessage = m;
        cs->firstmessage = m;
        m->prev = NULL;
    } //end if
    cs->numconsolemessages++;
} //end of the function BotQueueConsoleMessage
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm)
{
    bot_chatstate_t *cs;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return 0;
    if (cs->firstmessage)
    {
        Com_Memcpy(cm, cs->firstmessage, sizeof(bot_consolemessage_t));
        cm->next = cm->prev = NULL;
        return cm->handle;
    } //end if
    return 0;
} //end of the function BotConsoleMessage
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BotNumConsoleMessages(int chatstate)
{
    bot_chatstate_t *cs;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return 0;
    return cs->numconsolemessages;
} //end of the function BotNumConsoleMessages
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int IsWhiteSpace(char c)
{
    if ((c >= 'a' && c <= 'z')
            || (c >= 'A' && c <= 'Z')
            || (c >= '0' && c <= '9')
            || c == '(' || c == ')'
            || c == '?' || c == ':'
            || c == '\''|| c == '/'
            || c == ',' || c == '.'
            || c == '['	|| c == ']'
            || c == '-' || c == '_'
            || c == '+' || c == '=') return qfalse;
    return qtrue;
} //end of the function IsWhiteSpace
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotRemoveTildes(char *message)
{
    int i;

    //remove all tildes from the chat message
    for (i = 0; message[i]; i++)
    {
        if (message[i] == '~')
        {
            memmove(&message[i], &message[i+1], strlen(&message[i+1])+1);
        } //end if
    } //end for
} //end of the function BotRemoveTildes
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void UnifyWhiteSpaces(char *string)
{
    char *ptr, *oldptr;

    for (ptr = oldptr = string; *ptr; oldptr = ptr)
    {
        while(*ptr && IsWhiteSpace(*ptr)) ptr++;
        if (ptr > oldptr)
        {
            //if not at the start and not at the end of the string
            //write only one space
            if (oldptr > string && *ptr) *oldptr++ = ' ';
            //remove all other white spaces
            if (ptr > oldptr) memmove(oldptr, ptr, strlen(ptr)+1);
        } //end if
        while(*ptr && !IsWhiteSpace(*ptr)) ptr++;
    } //end while
} //end of the function UnifyWhiteSpaces
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int StringContains(char *str1, char *str2, int casesensitive)
{
    int len, i, j, index;

    if (str1 == NULL || str2 == NULL) return -1;

    len = strlen(str1) - strlen(str2);
    index = 0;
    for (i = 0; i <= len; i++, str1++, index++)
    {
        for (j = 0; str2[j]; j++)
        {
            if (casesensitive)
            {
                if (str1[j] != str2[j]) break;
            } //end if
            else
            {
                if (toupper(str1[j]) != toupper(str2[j])) break;
            } //end else
        } //end for
        if (!str2[j]) return index;
    } //end for
    return -1;
} //end of the function StringContains
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
char *StringContainsWord(char *str1, char *str2, int casesensitive)
{
    int len, i, j;

    len = strlen(str1) - strlen(str2);
    for (i = 0; i <= len; i++, str1++)
    {
        //if not at the start of the string
        if (i)
        {
            //skip to the start of the next word
            while(*str1 && *str1 != ' ' && *str1 != '.' && *str1 != ',' && *str1 != '!') str1++;
            if (!*str1) break;
            str1++;
        } //end for
        //compare the word
        for (j = 0; str2[j]; j++)
        {
            if (casesensitive)
            {
                if (str1[j] != str2[j]) break;
            } //end if
            else
            {
                if (toupper(str1[j]) != toupper(str2[j])) break;
            } //end else
        } //end for
        //if there was a word match
        if (!str2[j])
        {
            //if the first string has an end of word
            if (!str1[j] || str1[j] == ' ' || str1[j] == '.' || str1[j] == ',' || str1[j] == '!') return str1;
        } //end if
    } //end for
    return NULL;
} //end of the function StringContainsWord
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void StringReplaceWords(char *string, char *synonym, char *replacement)
{
    char *str, *str2;

    //find the synonym in the string
    str = StringContainsWord(string, synonym, qfalse);
    //if the synonym occured in the string
    while(str)
    {
        //if the synonym isn't part of the replacement which is already in the string
        //usefull for abreviations
        str2 = StringContainsWord(string, replacement, qfalse);
        while(str2)
        {
            if (str2 <= str && str < str2 + strlen(replacement)) break;
            str2 = StringContainsWord(str2+1, replacement, qfalse);
        } //end while
        if (!str2)
        {
            memmove(str + strlen(replacement), str+strlen(synonym), strlen(str+strlen(synonym))+1);
            //append the synonum replacement
            Com_Memcpy(str, replacement, strlen(replacement));
        } //end if
        //find the next synonym in the string
        str = StringContainsWord(str+strlen(replacement), synonym, qfalse);
    } //end if
} //end of the function StringReplaceWords
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotDumpSynonymList(bot_synonymlist_t *synlist)
{
    FILE *fp;
    bot_synonymlist_t *syn;
    bot_synonym_t *synonym;

    fp = Log_FilePointer();
    if (!fp) return;
    for (syn = synlist; syn; syn = syn->next)
    {
        fprintf(fp, "%ld : [", syn->context);
        for (synonym = syn->firstsynonym; synonym; synonym = synonym->next)
        {
            fprintf(fp, "(\"%s\", %1.2f)", synonym->string, synonym->weight);
            if (synonym->next) fprintf(fp, ", ");
        } //end for
        fprintf(fp, "]\n");
    } //end for
} //end of the function BotDumpSynonymList
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_synonymlist_t *BotLoadSynonyms(char *filename)
{
    int pass, size, contextlevel, numsynonyms;
    unsigned long int context, contextstack[32];
    char *ptr = NULL;
    source_t *source;
    token_t token;
    bot_synonymlist_t *synlist, *lastsyn, *syn;
    bot_synonym_t *synonym, *lastsynonym;

    size = 0;
    synlist = NULL; //make compiler happy
    syn = NULL; //make compiler happy
    synonym = NULL; //make compiler happy
    //the synonyms are parsed in two phases
    for (pass = 0; pass < 2; pass++)
    {
        //
        if (pass && size) ptr = (char *) GetClearedHunkMemory(size);
        //
        PC_SetBaseFolder(BOTFILESBASEFOLDER);
        source = LoadSourceFile(filename);
        if (!source)
        {
            botimport.Print(PRT_ERROR, "counldn't load %s\n", filename);
            return NULL;
        } //end if
        //
        context = 0;
        contextlevel = 0;
        synlist = NULL; //list synonyms
        lastsyn = NULL; //last synonym in the list
        //
        while(PC_ReadToken(source, &token))
        {
            if (token.type == TT_NUMBER)
            {
                context |= token.intvalue;
                contextstack[contextlevel] = token.intvalue;
                contextlevel++;
                if (contextlevel >= 32)
                {
                    SourceError(source, "more than 32 context levels");
                    FreeSource(source);
                    return NULL;
                } //end if
                if (!PC_ExpectTokenString(source, "{"))
                {
                    FreeSource(source);
                    return NULL;
                } //end if
            } //end if
            else if (token.type == TT_PUNCTUATION)
            {
                if (!strcmp(token.string, "}"))
                {
                    contextlevel--;
                    if (contextlevel < 0)
                    {
                        SourceError(source, "too many }");
                        FreeSource(source);
                        return NULL;
                    } //end if
                    context &= ~contextstack[contextlevel];
                } //end if
                else if (!strcmp(token.string, "["))
                {
                    size += sizeof(bot_synonymlist_t);
                    if (pass)
                    {
                        syn = (bot_synonymlist_t *) ptr;
                        ptr += sizeof(bot_synonymlist_t);
                        syn->context = context;
                        syn->firstsynonym = NULL;
                        syn->next = NULL;
                        if (lastsyn) lastsyn->next = syn;
                        else synlist = syn;
                        lastsyn = syn;
                    } //end if
                    numsynonyms = 0;
                    lastsynonym = NULL;
                    while(1)
                    {
                        if (!PC_ExpectTokenString(source, "(") ||
                                !PC_ExpectTokenType(source, TT_STRING, 0, &token))
                        {
                            FreeSource(source);
                            return NULL;
                        } //end if
                        StripDoubleQuotes(token.string);
                        if (strlen(token.string) <= 0)
                        {
                            SourceError(source, "empty string", token.string);
                            FreeSource(source);
                            return NULL;
                        } //end if
                        size += sizeof(bot_synonym_t) + strlen(token.string) + 1;
                        if (pass)
                        {
                            synonym = (bot_synonym_t *) ptr;
                            ptr += sizeof(bot_synonym_t);
                            synonym->string = ptr;
                            ptr += strlen(token.string) + 1;
                            strcpy(synonym->string, token.string);
                            //
                            if (lastsynonym) lastsynonym->next = synonym;
                            else syn->firstsynonym = synonym;
                            lastsynonym = synonym;
                        } //end if
                        numsynonyms++;
                        if (!PC_ExpectTokenString(source, ",") ||
                                !PC_ExpectTokenType(source, TT_NUMBER, 0, &token) ||
                                !PC_ExpectTokenString(source, ")"))
                        {
                            FreeSource(source);
                            return NULL;
                        } //end if
                        if (pass)
                        {
                            synonym->weight = token.floatvalue;
                            syn->totalweight += synonym->weight;
                        } //end if
                        if (PC_CheckTokenString(source, "]")) break;
                        if (!PC_ExpectTokenString(source, ","))
                        {
                            FreeSource(source);
                            return NULL;
                        } //end if
                    } //end while
                    if (numsynonyms < 2)
                    {
                        SourceError(source, "synonym must have at least two entries\n");
                        FreeSource(source);
                        return NULL;
                    } //end if
                } //end else
                else
                {
                    SourceError(source, "unexpected %s", token.string);
                    FreeSource(source);
                    return NULL;
                } //end if
            } //end else if
        } //end while
        //
        FreeSource(source);
        //
        if (contextlevel > 0)
        {
            SourceError(source, "missing }");
            return NULL;
        } //end if
    } //end for
    botimport.Print(PRT_MESSAGE, "loaded %s\n", filename);
    //
    //BotDumpSynonymList(synlist);
    //
    return synlist;
} //end of the function BotLoadSynonyms
//===========================================================================
// replace all the synonyms in the string
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotReplaceSynonyms(char *string, unsigned long int context)
{
    bot_synonymlist_t *syn;
    bot_synonym_t *synonym;

    for (syn = synonyms; syn; syn = syn->next)
    {
        if (!(syn->context & context)) continue;
        for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next)
        {
            StringReplaceWords(string, synonym->string, syn->firstsynonym->string);
        } //end for
    } //end for
} //end of the function BotReplaceSynonyms
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotReplaceWeightedSynonyms(char *string, unsigned long int context)
{
    bot_synonymlist_t *syn;
    bot_synonym_t *synonym, *replacement;
    float weight, curweight;

    for (syn = synonyms; syn; syn = syn->next)
    {
        if (!(syn->context & context)) continue;
        //choose a weighted random replacement synonym
        weight = random() * syn->totalweight;
        if (!weight) continue;
        curweight = 0;
        for (replacement = syn->firstsynonym; replacement; replacement = replacement->next)
        {
            curweight += replacement->weight;
            if (weight < curweight) break;
        } //end for
        if (!replacement) continue;
        //replace all synonyms with the replacement
        for (synonym = syn->firstsynonym; synonym; synonym = synonym->next)
        {
            if (synonym == replacement) continue;
            StringReplaceWords(string, synonym->string, replacement->string);
        } //end for
    } //end for
} //end of the function BotReplaceWeightedSynonyms
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotReplaceReplySynonyms(char *string, unsigned long int context)
{
    char *str1, *str2, *replacement;
    bot_synonymlist_t *syn;
    bot_synonym_t *synonym;

    for (str1 = string; *str1; )
    {
        //go to the start of the next word
        while(*str1 && *str1 <= ' ') str1++;
        if (!*str1) break;
        //
        for (syn = synonyms; syn; syn = syn->next)
        {
            if (!(syn->context & context)) continue;
            for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next)
            {
                str2 = synonym->string;
                //if the synonym is not at the front of the string continue
                str2 = StringContainsWord(str1, synonym->string, qfalse);
                if (!str2 || str2 != str1) continue;
                //
                replacement = syn->firstsynonym->string;
                //if the replacement IS in front of the string continue
                str2 = StringContainsWord(str1, replacement, qfalse);
                if (str2 && str2 == str1) continue;
                //
                memmove(str1 + strlen(replacement), str1+strlen(synonym->string),
                        strlen(str1+strlen(synonym->string)) + 1);
                //append the synonum replacement
                Com_Memcpy(str1, replacement, strlen(replacement));
                //
                break;
            } //end for
            //if a synonym has been replaced
            if (synonym) break;
        } //end for
        //skip over this word
        while(*str1 && *str1 > ' ') str1++;
        if (!*str1) break;
    } //end while
} //end of the function BotReplaceReplySynonyms
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotLoadChatMessage(source_t *source, char *chatmessagestring)
{
    char *ptr;
    token_t token;

    ptr = chatmessagestring;
    *ptr = 0;
    //
    while(1)
    {
        if (!PC_ExpectAnyToken(source, &token)) return qfalse;
        //fixed string
        if (token.type == TT_STRING)
        {
            StripDoubleQuotes(token.string);
            if (strlen(ptr) + strlen(token.string) + 1 > MAX_MESSAGE_SIZE)
            {
                SourceError(source, "chat message too long\n");
                return qfalse;
            } //end if
            strcat(ptr, token.string);
        } //end else if
        //variable string
        else if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER))
        {
            if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE)
            {
                SourceError(source, "chat message too long\n");
                return qfalse;
            } //end if
            sprintf(&ptr[strlen(ptr)], "%cv%ld%c", ESCAPE_CHAR, token.intvalue, ESCAPE_CHAR);
        } //end if
        //random string
        else if (token.type == TT_NAME)
        {
            if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE)
            {
                SourceError(source, "chat message too long\n");
                return qfalse;
            } //end if
            sprintf(&ptr[strlen(ptr)], "%cr%s%c", ESCAPE_CHAR, token.string, ESCAPE_CHAR);
        } //end else if
        else
        {
            SourceError(source, "unknown message component %s\n", token.string);
            return qfalse;
        } //end else
        if (PC_CheckTokenString(source, ";")) break;
        if (!PC_ExpectTokenString(source, ",")) return qfalse;
    } //end while
    //
    return qtrue;
} //end of the function BotLoadChatMessage
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotDumpRandomStringList(bot_randomlist_t *randomlist)
{
    FILE *fp;
    bot_randomlist_t *random;
    bot_randomstring_t *rs;

    fp = Log_FilePointer();
    if (!fp) return;
    for (random = randomlist; random; random = random->next)
    {
        fprintf(fp, "%s = {", random->string);
        for (rs = random->firstrandomstring; rs; rs = rs->next)
        {
            fprintf(fp, "\"%s\"", rs->string);
            if (rs->next) fprintf(fp, ", ");
            else fprintf(fp, "}\n");
        } //end for
    } //end for
} //end of the function BotDumpRandomStringList
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_randomlist_t *BotLoadRandomStrings(char *filename)
{
    int pass, size;
    char *ptr = NULL, chatmessagestring[MAX_MESSAGE_SIZE];
    source_t *source;
    token_t token;
    bot_randomlist_t *randomlist, *lastrandom, *random;
    bot_randomstring_t *randomstring;

#ifdef DEBUG
    int starttime = Sys_MilliSeconds();
#endif //DEBUG

    size = 0;
    randomlist = NULL;
    random = NULL;
    //the synonyms are parsed in two phases
    for (pass = 0; pass < 2; pass++)
    {
        //
        if (pass && size) ptr = (char *) GetClearedHunkMemory(size);
        //
        PC_SetBaseFolder(BOTFILESBASEFOLDER);
        source = LoadSourceFile(filename);
        if (!source)
        {
            botimport.Print(PRT_ERROR, "counldn't load %s\n", filename);
            return NULL;
        } //end if
        //
        randomlist = NULL; //list
        lastrandom = NULL; //last
        //
        while(PC_ReadToken(source, &token))
        {
            if (token.type != TT_NAME)
            {
                SourceError(source, "unknown random %s", token.string);
                FreeSource(source);
                return NULL;
            } //end if
            size += sizeof(bot_randomlist_t) + strlen(token.string) + 1;
            if (pass)
            {
                random = (bot_randomlist_t *) ptr;
                ptr += sizeof(bot_randomlist_t);
                random->string = ptr;
                ptr += strlen(token.string) + 1;
                strcpy(random->string, token.string);
                random->firstrandomstring = NULL;
                random->numstrings = 0;
                //
                if (lastrandom) lastrandom->next = random;
                else randomlist = random;
                lastrandom = random;
            } //end if
            if (!PC_ExpectTokenString(source, "=") ||
                    !PC_ExpectTokenString(source, "{"))
            {
                FreeSource(source);
                return NULL;
            } //end if
            while(!PC_CheckTokenString(source, "}"))
            {
                if (!BotLoadChatMessage(source, chatmessagestring))
                {
                    FreeSource(source);
                    return NULL;
                } //end if
                size += sizeof(bot_randomstring_t) + strlen(chatmessagestring) + 1;
                if (pass)
                {
                    randomstring = (bot_randomstring_t *) ptr;
                    ptr += sizeof(bot_randomstring_t);
                    randomstring->string = ptr;
                    ptr += strlen(chatmessagestring) + 1;
                    strcpy(randomstring->string, chatmessagestring);
                    //
                    random->numstrings++;
                    randomstring->next = random->firstrandomstring;
                    random->firstrandomstring = randomstring;
                } //end if
            } //end while
        } //end while
        //free the source after one pass
        FreeSource(source);
    } //end for
    botimport.Print(PRT_MESSAGE, "loaded %s\n", filename);
    //
#ifdef DEBUG
    botimport.Print(PRT_MESSAGE, "random strings %d msec\n", Sys_MilliSeconds() - starttime);
    //BotDumpRandomStringList(randomlist);
#endif //DEBUG
    //
    return randomlist;
} //end of the function BotLoadRandomStrings
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
char *RandomString(char *name)
{
    bot_randomlist_t *random;
    bot_randomstring_t *rs;
    int i;

    for (random = randomstrings; random; random = random->next)
    {
        if (!strcmp(random->string, name))
        {
            i = (int)(random() * random->numstrings);
            for (rs = random->firstrandomstring; rs; rs = rs->next)
            {
                if (--i < 0) break;
            } //end for
            if (rs)
            {
                return rs->string;
            } //end if
        } //end for
    } //end for
    return NULL;
} //end of the function RandomString
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotDumpMatchTemplates(bot_matchtemplate_t *matches)
{
    FILE *fp;
    bot_matchtemplate_t *mt;
    bot_matchpiece_t *mp;
    bot_matchstring_t *ms;

    fp = Log_FilePointer();
    if (!fp) return;
    for (mt = matches; mt; mt = mt->next)
    {
        fprintf(fp, "{ " );
        for (mp = mt->first; mp; mp = mp->next)
        {
            if (mp->type == MT_STRING)
            {
                for (ms = mp->firststring; ms; ms = ms->next)
                {
                    fprintf(fp, "\"%s\"", ms->string);
                    if (ms->next) fprintf(fp, "|");
                } //end for
            } //end if
            else if (mp->type == MT_VARIABLE)
            {
                fprintf(fp, "%d", mp->variable);
            } //end else if
            if (mp->next) fprintf(fp, ", ");
        } //end for
        fprintf(fp, " = (%d, %d);}\n", mt->type, mt->subtype);
    } //end for
} //end of the function BotDumpMatchTemplates
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotFreeMatchPieces(bot_matchpiece_t *matchpieces)
{
    bot_matchpiece_t *mp, *nextmp;
    bot_matchstring_t *ms, *nextms;

    for (mp = matchpieces; mp; mp = nextmp)
    {
        nextmp = mp->next;
        if (mp->type == MT_STRING)
        {
            for (ms = mp->firststring; ms; ms = nextms)
            {
                nextms = ms->next;
                FreeMemory(ms);
            } //end for
        } //end if
        FreeMemory(mp);
    } //end for
} //end of the function BotFreeMatchPieces
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_matchpiece_t *BotLoadMatchPieces(source_t *source, char *endtoken)
{
    int lastwasvariable, emptystring;
    token_t token;
    bot_matchpiece_t *matchpiece, *firstpiece, *lastpiece;
    bot_matchstring_t *matchstring, *lastmatchstring;

    firstpiece = NULL;
    lastpiece = NULL;
    //
    lastwasvariable = qfalse;
    //
    while(PC_ReadToken(source, &token))
    {
        if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER))
        {
            if (token.intvalue < 0 || token.intvalue >= MAX_MATCHVARIABLES)
            {
                SourceError(source, "can't have more than %d match variables\n", MAX_MATCHVARIABLES);
                FreeSource(source);
                BotFreeMatchPieces(firstpiece);
                return NULL;
            } //end if
            if (lastwasvariable)
            {
                SourceError(source, "not allowed to have adjacent variables\n");
                FreeSource(source);
                BotFreeMatchPieces(firstpiece);
                return NULL;
            } //end if
            lastwasvariable = qtrue;
            //
            matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t));
            matchpiece->type = MT_VARIABLE;
            matchpiece->variable = token.intvalue;
            matchpiece->next = NULL;
            if (lastpiece) lastpiece->next = matchpiece;
            else firstpiece = matchpiece;
            lastpiece = matchpiece;
        } //end if
        else if (token.type == TT_STRING)
        {
            //
            matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t));
            matchpiece->firststring = NULL;
            matchpiece->type = MT_STRING;
            matchpiece->variable = 0;
            matchpiece->next = NULL;
            if (lastpiece) lastpiece->next = matchpiece;
            else firstpiece = matchpiece;
            lastpiece = matchpiece;
            //
            lastmatchstring = NULL;
            emptystring = qfalse;
            //
            do
            {
                if (matchpiece->firststring)
                {
                    if (!PC_ExpectTokenType(source, TT_STRING, 0, &token))
                    {
                        FreeSource(source);
                        BotFreeMatchPieces(firstpiece);
                        return NULL;
                    } //end if
                } //end if
                StripDoubleQuotes(token.string);
                matchstring = (bot_matchstring_t *) GetClearedHunkMemory(sizeof(bot_matchstring_t) + strlen(token.string) + 1);
                matchstring->string = (char *) matchstring + sizeof(bot_matchstring_t);
                strcpy(matchstring->string, token.string);
                if (!strlen(token.string)) emptystring = qtrue;
                matchstring->next = NULL;
                if (lastmatchstring) lastmatchstring->next = matchstring;
                else matchpiece->firststring = matchstring;
                lastmatchstring = matchstring;
            } while(PC_CheckTokenString(source, "|"));
            //if there was no empty string found
            if (!emptystring) lastwasvariable = qfalse;
        } //end if
        else
        {
            SourceError(source, "invalid token %s\n", token.string);
            FreeSource(source);
            BotFreeMatchPieces(firstpiece);
            return NULL;
        } //end else
        if (PC_CheckTokenString(source, endtoken)) break;
        if (!PC_ExpectTokenString(source, ","))
        {
            FreeSource(source);
            BotFreeMatchPieces(firstpiece);
            return NULL;
        } //end if
    } //end while
    return firstpiece;
} //end of the function BotLoadMatchPieces
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotFreeMatchTemplates(bot_matchtemplate_t *mt)
{
    bot_matchtemplate_t *nextmt;

    for (; mt; mt = nextmt)
    {
        nextmt = mt->next;
        BotFreeMatchPieces(mt->first);
        FreeMemory(mt);
    } //end for
} //end of the function BotFreeMatchTemplates
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_matchtemplate_t *BotLoadMatchTemplates(char *matchfile)
{
    source_t *source;
    token_t token;
    bot_matchtemplate_t *matchtemplate, *matches, *lastmatch;
    unsigned long int context;

    PC_SetBaseFolder(BOTFILESBASEFOLDER);
    source = LoadSourceFile(matchfile);
    if (!source)
    {
        botimport.Print(PRT_ERROR, "counldn't load %s\n", matchfile);
        return NULL;
    } //end if
    //
    matches = NULL; //list with matches
    lastmatch = NULL; //last match in the list

    while(PC_ReadToken(source, &token))
    {
        if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER))
        {
            SourceError(source, "expected integer, found %s\n", token.string);
            BotFreeMatchTemplates(matches);
            FreeSource(source);
            return NULL;
        } //end if
        //the context
        context = token.intvalue;
        //
        if (!PC_ExpectTokenString(source, "{"))
        {
            BotFreeMatchTemplates(matches);
            FreeSource(source);
            return NULL;
        } //end if
        //
        while(PC_ReadToken(source, &token))
        {
            if (!strcmp(token.string, "}")) break;
            //
            PC_UnreadLastToken(source);
            //
            matchtemplate = (bot_matchtemplate_t *) GetClearedHunkMemory(sizeof(bot_matchtemplate_t));
            matchtemplate->context = context;
            matchtemplate->next = NULL;
            //add the match template to the list
            if (lastmatch) lastmatch->next = matchtemplate;
            else matches = matchtemplate;
            lastmatch = matchtemplate;
            //load the match template
            matchtemplate->first = BotLoadMatchPieces(source, "=");
            if (!matchtemplate->first)
            {
                BotFreeMatchTemplates(matches);
                return NULL;
            } //end if
            //read the match type
            if (!PC_ExpectTokenString(source, "(") ||
                    !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token))
            {
                BotFreeMatchTemplates(matches);
                FreeSource(source);
                return NULL;
            } //end if
            matchtemplate->type = token.intvalue;
            //read the match subtype
            if (!PC_ExpectTokenString(source, ",") ||
                    !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token))
            {
                BotFreeMatchTemplates(matches);
                FreeSource(source);
                return NULL;
            } //end if
            matchtemplate->subtype = token.intvalue;
            //read trailing punctuations
            if (!PC_ExpectTokenString(source, ")") ||
                    !PC_ExpectTokenString(source, ";"))
            {
                BotFreeMatchTemplates(matches);
                FreeSource(source);
                return NULL;
            } //end if
        } //end while
    } //end while
    //free the source
    FreeSource(source);
    botimport.Print(PRT_MESSAGE, "loaded %s\n", matchfile);
    //
    //BotDumpMatchTemplates(matches);
    //
    return matches;
} //end of the function BotLoadMatchTemplates
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int StringsMatch(bot_matchpiece_t *pieces, bot_match_t *match)
{
    int lastvariable, index;
    char *strptr, *newstrptr;
    bot_matchpiece_t *mp;
    bot_matchstring_t *ms;

    //no last variable
    lastvariable = -1;
    //pointer to the string to compare the match string with
    strptr = match->string;
    //Log_Write("match: %s", strptr);
    //compare the string with the current match string
    for (mp = pieces; mp; mp = mp->next)
    {
        //if it is a piece of string
        if (mp->type == MT_STRING)
        {
            newstrptr = NULL;
            for (ms = mp->firststring; ms; ms = ms->next)
            {
                if (!strlen(ms->string))
                {
                    newstrptr = strptr;
                    break;
                } //end if
                //Log_Write("MT_STRING: %s", mp->string);
                index = StringContains(strptr, ms->string, qfalse);
                if (index >= 0)
                {
                    newstrptr = strptr + index;
                    if (lastvariable >= 0)
                    {
                        match->variables[lastvariable].length =
                            (newstrptr - match->string) - match->variables[lastvariable].offset;
                        //newstrptr - match->variables[lastvariable].ptr;
                        lastvariable = -1;
                        break;
                    } //end if
                    else if (index == 0)
                    {
                        break;
                    } //end else
                    newstrptr = NULL;
                } //end if
            } //end for
            if (!newstrptr) return qfalse;
            strptr = newstrptr + strlen(ms->string);
        } //end if
        //if it is a variable piece of string
        else if (mp->type == MT_VARIABLE)
        {
            //Log_Write("MT_VARIABLE");
            match->variables[mp->variable].offset = strptr - match->string;
            lastvariable = mp->variable;
        } //end else if
    } //end for
    //if a match was found
    if (!mp && (lastvariable >= 0 || !strlen(strptr)))
    {
        //if the last piece was a variable string
        if (lastvariable >= 0)
        {
            assert( match->variables[lastvariable].offset >= 0 ); // bk001204
            match->variables[lastvariable].length =
                strlen(&match->string[ (int) match->variables[lastvariable].offset]);
        } //end if
        return qtrue;
    } //end if
    return qfalse;
} //end of the function StringsMatch
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BotFindMatch(char *str, bot_match_t *match, unsigned long int context)
{
    int i;
    bot_matchtemplate_t *ms;

    strncpy(match->string, str, MAX_MESSAGE_SIZE);
    //remove any trailing enters
    while(strlen(match->string) &&
            match->string[strlen(match->string)-1] == '\n')
    {
        match->string[strlen(match->string)-1] = '\0';
    } //end while
    //compare the string with all the match strings
    for (ms = matchtemplates; ms; ms = ms->next)
    {
        if (!(ms->context & context)) continue;
        //reset the match variable offsets
        for (i = 0; i < MAX_MATCHVARIABLES; i++) match->variables[i].offset = -1;
        //
        if (StringsMatch(ms->first, match))
        {
            match->type = ms->type;
            match->subtype = ms->subtype;
            return qtrue;
        } //end if
    } //end for
    return qfalse;
} //end of the function BotFindMatch
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size)
{
    if (variable < 0 || variable >= MAX_MATCHVARIABLES)
    {
        botimport.Print(PRT_FATAL, "BotMatchVariable: variable out of range\n");
        strcpy(buf, "");
        return;
    } //end if

    if (match->variables[variable].offset >= 0)
    {
        if (match->variables[variable].length < size)
            size = match->variables[variable].length+1;
        assert( match->variables[variable].offset >= 0 ); // bk001204
        strncpy(buf, &match->string[ (int) match->variables[variable].offset], size-1);
        buf[size-1] = '\0';
    } //end if
    else
    {
        strcpy(buf, "");
    } //end else
    return;
} //end of the function BotMatchVariable
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_stringlist_t *BotFindStringInList(bot_stringlist_t *list, char *string)
{
    bot_stringlist_t *s;

    for (s = list; s; s = s->next)
    {
        if (!strcmp(s->string, string)) return s;
    } //end for
    return NULL;
} //end of the function BotFindStringInList
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_stringlist_t *BotCheckChatMessageIntegrety(char *message, bot_stringlist_t *stringlist)
{
    int i;
    char *msgptr;
    char temp[MAX_MESSAGE_SIZE];
    bot_stringlist_t *s;

    msgptr = message;
    //
    while(*msgptr)
    {
        if (*msgptr == ESCAPE_CHAR)
        {
            msgptr++;
            switch(*msgptr)
            {
            case 'v': //variable
                {
                    //step over the 'v'
                    msgptr++;
                    while(*msgptr && *msgptr != ESCAPE_CHAR) msgptr++;
                    //step over the trailing escape char
                    if (*msgptr) msgptr++;
                    break;
                } //end case
            case 'r': //random
                {
                    //step over the 'r'
                    msgptr++;
                    for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++)
                    {
                        temp[i] = *msgptr++;
                    } //end while
                    temp[i] = '\0';
                    //step over the trailing escape char
                    if (*msgptr) msgptr++;
                    //find the random keyword
                    if (!RandomString(temp))
                    {
                        if (!BotFindStringInList(stringlist, temp))
                        {
                            Log_Write("%s = {\"%s\"} //MISSING RANDOM\r\n", temp, temp);
                            s = (bot_stringlist_t *)GetClearedMemory(sizeof(bot_stringlist_t) + strlen(temp) + 1);
                            s->string = (char *) s + sizeof(bot_stringlist_t);
                            strcpy(s->string, temp);
                            s->next = stringlist;
                            stringlist = s;
                        } //end if
                    } //end if
                    break;
                } //end case
            default:
                {
                    botimport.Print(PRT_FATAL, "BotCheckChatMessageIntegrety: message \"%s\" invalid escape char\n", message);
                    break;
                } //end default
            } //end switch
        } //end if
        else
        {
            msgptr++;
        } //end else
    } //end while
    return stringlist;
} //end of the function BotCheckChatMessageIntegrety
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotCheckInitialChatIntegrety(bot_chat_t *chat)
{
    bot_chattype_t *t;
    bot_chatmessage_t *cm;
    bot_stringlist_t *stringlist, *s, *nexts;

    stringlist = NULL;
    for (t = chat->types; t; t = t->next)
    {
        for (cm = t->firstchatmessage; cm; cm = cm->next)
        {
            stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist);
        } //end for
    } //end for
    for (s = stringlist; s; s = nexts)
    {
        nexts = s->next;
        FreeMemory(s);
    } //end for
} //end of the function BotCheckInitialChatIntegrety
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotCheckReplyChatIntegrety(bot_replychat_t *replychat)
{
    bot_replychat_t *rp;
    bot_chatmessage_t *cm;
    bot_stringlist_t *stringlist, *s, *nexts;

    stringlist = NULL;
    for (rp = replychat; rp; rp = rp->next)
    {
        for (cm = rp->firstchatmessage; cm; cm = cm->next)
        {
            stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist);
        } //end for
    } //end for
    for (s = stringlist; s; s = nexts)
    {
        nexts = s->next;
        FreeMemory(s);
    } //end for
} //end of the function BotCheckReplyChatIntegrety
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotDumpReplyChat(bot_replychat_t *replychat)
{
    FILE *fp;
    bot_replychat_t *rp;
    bot_replychatkey_t *key;
    bot_chatmessage_t *cm;
    bot_matchpiece_t *mp;

    fp = Log_FilePointer();
    if (!fp) return;
    fprintf(fp, "BotDumpReplyChat:\n");
    for (rp = replychat; rp; rp = rp->next)
    {
        fprintf(fp, "[");
        for (key = rp->keys; key; key = key->next)
        {
            if (key->flags & RCKFL_AND) fprintf(fp, "&");
            else if (key->flags & RCKFL_NOT) fprintf(fp, "!");
            //
            if (key->flags & RCKFL_NAME) fprintf(fp, "name");
            else if (key->flags & RCKFL_GENDERFEMALE) fprintf(fp, "female");
            else if (key->flags & RCKFL_GENDERMALE) fprintf(fp, "male");
            else if (key->flags & RCKFL_GENDERLESS) fprintf(fp, "it");
            else if (key->flags & RCKFL_VARIABLES)
            {
                fprintf(fp, "(");
                for (mp = key->match; mp; mp = mp->next)
                {
                    if (mp->type == MT_STRING) fprintf(fp, "\"%s\"", mp->firststring->string);
                    else fprintf(fp, "%d", mp->variable);
                    if (mp->next) fprintf(fp, ", ");
                } //end for
                fprintf(fp, ")");
            } //end if
            else if (key->flags & RCKFL_STRING)
            {
                fprintf(fp, "\"%s\"", key->string);
            } //end if
            if (key->next) fprintf(fp, ", ");
            else fprintf(fp, "] = %1.0f\n", rp->priority);
        } //end for
        fprintf(fp, "{\n");
        for (cm = rp->firstchatmessage; cm; cm = cm->next)
        {
            fprintf(fp, "\t\"%s\";\n", cm->chatmessage);
        } //end for
        fprintf(fp, "}\n");
    } //end for
} //end of the function BotDumpReplyChat
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotFreeReplyChat(bot_replychat_t *replychat)
{
    bot_replychat_t *rp, *nextrp;
    bot_replychatkey_t *key, *nextkey;
    bot_chatmessage_t *cm, *nextcm;

    for (rp = replychat; rp; rp = nextrp)
    {
        nextrp = rp->next;
        for (key = rp->keys; key; key = nextkey)
        {
            nextkey = key->next;
            if (key->match) BotFreeMatchPieces(key->match);
            if (key->string) FreeMemory(key->string);
            FreeMemory(key);
        } //end for
        for (cm = rp->firstchatmessage; cm; cm = nextcm)
        {
            nextcm = cm->next;
            FreeMemory(cm);
        } //end for
        FreeMemory(rp);
    } //end for
} //end of the function BotFreeReplyChat
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotCheckValidReplyChatKeySet(source_t *source, bot_replychatkey_t *keys)
{
    int allprefixed, hasvariableskey, hasstringkey;
    bot_matchpiece_t *m;
    bot_matchstring_t *ms;
    bot_replychatkey_t *key, *key2;

    //
    allprefixed = qtrue;
    hasvariableskey = hasstringkey = qfalse;
    for (key = keys; key; key = key->next)
    {
        if (!(key->flags & (RCKFL_AND|RCKFL_NOT)))
        {
            allprefixed = qfalse;
            if (key->flags & RCKFL_VARIABLES)
            {
                for (m = key->match; m; m = m->next)
                {
                    if (m->type == MT_VARIABLE) hasvariableskey = qtrue;
                } //end for
            } //end if
            else if (key->flags & RCKFL_STRING)
            {
                hasstringkey = qtrue;
            } //end else if
        } //end if
        else if ((key->flags & RCKFL_AND) && (key->flags & RCKFL_STRING))
        {
            for (key2 = keys; key2; key2 = key2->next)
            {
                if (key2 == key) continue;
                if (key2->flags & RCKFL_NOT) continue;
                if (key2->flags & RCKFL_VARIABLES)
                {
                    for (m = key2->match; m; m = m->next)
                    {
                        if (m->type == MT_STRING)
                        {
                            for (ms = m->firststring; ms; ms = ms->next)
                            {
                                if (StringContains(ms->string, key->string, qfalse) != -1)
                                {
                                    break;
                                } //end if
                            } //end for
                            if (ms) break;
                        } //end if
                        else if (m->type == MT_VARIABLE)
                        {
                            break;
                        } //end if
                    } //end for
                    if (!m)
                    {
                        SourceWarning(source, "one of the match templates does not "
                                      "leave space for the key %s with the & prefix", key->string);
                    } //end if
                } //end if
            } //end for
        } //end else
        if ((key->flags & RCKFL_NOT) && (key->flags & RCKFL_STRING))
        {
            for (key2 = keys; key2; key2 = key2->next)
            {
                if (key2 == key) continue;
                if (key2->flags & RCKFL_NOT) continue;
                if (key2->flags & RCKFL_STRING)
                {
                    if (StringContains(key2->string, key->string, qfalse) != -1)
                    {
                        SourceWarning(source, "the key %s with prefix ! is inside the key %s", key->string, key2->string);
                    } //end if
                } //end if
                else if (key2->flags & RCKFL_VARIABLES)
                {
                    for (m = key2->match; m; m = m->next)
                    {
                        if (m->type == MT_STRING)
                        {
                            for (ms = m->firststring; ms; ms = ms->next)
                            {
                                if (StringContains(ms->string, key->string, qfalse) != -1)
                                {
                                    SourceWarning(source, "the key %s with prefix ! is inside "
                                                  "the match template string %s", key->string, ms->string);
                                } //end if
                            } //end for
                        } //end if
                    } //end for
                } //end else if
            } //end for
        } //end if
    } //end for
    if (allprefixed) SourceWarning(source, "all keys have a & or ! prefix");
    if (hasvariableskey && hasstringkey)
    {
        SourceWarning(source, "variables from the match template(s) could be "
                      "invalid when outputting one of the chat messages");
    } //end if
} //end of the function BotCheckValidReplyChatKeySet
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_replychat_t *BotLoadReplyChat(char *filename)
{
    char chatmessagestring[MAX_MESSAGE_SIZE];
    char namebuffer[MAX_MESSAGE_SIZE];
    source_t *source;
    token_t token;
    bot_chatmessage_t *chatmessage = NULL;
    bot_replychat_t *replychat, *replychatlist;
    bot_replychatkey_t *key;

    PC_SetBaseFolder(BOTFILESBASEFOLDER);
    source = LoadSourceFile(filename);
    if (!source)
    {
        botimport.Print(PRT_ERROR, "counldn't load %s\n", filename);
        return NULL;
    } //end if
    //
    replychatlist = NULL;
    //
    while(PC_ReadToken(source, &token))
    {
        if (strcmp(token.string, "["))
        {
            SourceError(source, "expected [, found %s", token.string);
            BotFreeReplyChat(replychatlist);
            FreeSource(source);
            return NULL;
        } //end if
        //
        replychat = (bot_replychat_t *)GetClearedHunkMemory(sizeof(bot_replychat_t));
        replychat->keys = NULL;
        replychat->next = replychatlist;
        replychatlist = replychat;
        //read the keys, there must be at least one key
        do
        {
            //allocate a key
            key = (bot_replychatkey_t *) GetClearedHunkMemory(sizeof(bot_replychatkey_t));
            key->flags = 0;
            key->string = NULL;
            key->match = NULL;
            key->next = replychat->keys;
            replychat->keys = key;
            //check for MUST BE PRESENT and MUST BE ABSENT keys
            if (PC_CheckTokenString(source, "&")) key->flags |= RCKFL_AND;
            else if (PC_CheckTokenString(source, "!")) key->flags |= RCKFL_NOT;
            //special keys
            if (PC_CheckTokenString(source, "name")) key->flags |= RCKFL_NAME;
            else if (PC_CheckTokenString(source, "female")) key->flags |= RCKFL_GENDERFEMALE;
            else if (PC_CheckTokenString(source, "male")) key->flags |= RCKFL_GENDERMALE;
            else if (PC_CheckTokenString(source, "it")) key->flags |= RCKFL_GENDERLESS;
            else if (PC_CheckTokenString(source, "(")) //match key
            {
                key->flags |= RCKFL_VARIABLES;
                key->match = BotLoadMatchPieces(source, ")");
                if (!key->match)
                {
                    BotFreeReplyChat(replychatlist);
                    return NULL;
                } //end if
            } //end else if
            else if (PC_CheckTokenString(source, "<")) //bot names
            {
                key->flags |= RCKFL_BOTNAMES;
                strcpy(namebuffer, "");
                do
                {
                    if (!PC_ExpectTokenType(source, TT_STRING, 0, &token))
                    {
                        BotFreeReplyChat(replychatlist);
                        FreeSource(source);
                        return NULL;
                    } //end if
                    StripDoubleQuotes(token.string);
                    if (strlen(namebuffer)) strcat(namebuffer, "\\");
                    strcat(namebuffer, token.string);
                } while(PC_CheckTokenString(source, ","));
                if (!PC_ExpectTokenString(source, ">"))
                {
                    BotFreeReplyChat(replychatlist);
                    FreeSource(source);
                    return NULL;
                } //end if
                key->string = (char *) GetClearedHunkMemory(strlen(namebuffer) + 1);
                strcpy(key->string, namebuffer);
            } //end else if
            else //normal string key
            {
                key->flags |= RCKFL_STRING;
                if (!PC_ExpectTokenType(source, TT_STRING, 0, &token))
                {
                    BotFreeReplyChat(replychatlist);
                    FreeSource(source);
                    return NULL;
                } //end if
                StripDoubleQuotes(token.string);
                key->string = (char *) GetClearedHunkMemory(strlen(token.string) + 1);
                strcpy(key->string, token.string);
            } //end else
            //
            PC_CheckTokenString(source, ",");
        } while(!PC_CheckTokenString(source, "]"));
        //
        BotCheckValidReplyChatKeySet(source, replychat->keys);
        //read the = sign and the priority
        if (!PC_ExpectTokenString(source, "=") ||
                !PC_ExpectTokenType(source, TT_NUMBER, 0, &token))
        {
            BotFreeReplyChat(replychatlist);
            FreeSource(source);
            return NULL;
        } //end if
        replychat->priority = token.floatvalue;
        //read the leading {
        if (!PC_ExpectTokenString(source, "{"))
        {
            BotFreeReplyChat(replychatlist);
            FreeSource(source);
            return NULL;
        } //end if
        replychat->numchatmessages = 0;
        //while the trailing } is not found
        while(!PC_CheckTokenString(source, "}"))
        {
            if (!BotLoadChatMessage(source, chatmessagestring))
            {
                BotFreeReplyChat(replychatlist);
                FreeSource(source);
                return NULL;
            } //end if
            chatmessage = (bot_chatmessage_t *) GetClearedHunkMemory(sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1);
            chatmessage->chatmessage = (char *) chatmessage + sizeof(bot_chatmessage_t);
            strcpy(chatmessage->chatmessage, chatmessagestring);
            chatmessage->time = -2*CHATMESSAGE_RECENTTIME;
            chatmessage->next = replychat->firstchatmessage;
            //add the chat message to the reply chat
            replychat->firstchatmessage = chatmessage;
            replychat->numchatmessages++;
        } //end while
    } //end while
    FreeSource(source);
    botimport.Print(PRT_MESSAGE, "loaded %s\n", filename);
    //
    //BotDumpReplyChat(replychatlist);
    if (bot_developer->integer)
    {
        BotCheckReplyChatIntegrety(replychatlist);
    } //end if
    //
    if (!replychatlist) botimport.Print(PRT_MESSAGE, "no rchats\n");
    //
    return replychatlist;
} //end of the function BotLoadReplyChat
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotDumpInitialChat(bot_chat_t *chat)
{
    bot_chattype_t *t;
    bot_chatmessage_t *m;

    Log_Write("{");
    for (t = chat->types; t; t = t->next)
    {
        Log_Write(" type \"%s\"", t->name);
        Log_Write(" {");
        Log_Write("  numchatmessages = %d", t->numchatmessages);
        for (m = t->firstchatmessage; m; m = m->next)
        {
            Log_Write("  \"%s\"", m->chatmessage);
        } //end for
        Log_Write(" }");
    } //end for
    Log_Write("}");
} //end of the function BotDumpInitialChat
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_chat_t *BotLoadInitialChat(char *chatfile, char *chatname)
{
    int pass, foundchat, indent, size;
    char *ptr = NULL;
    char chatmessagestring[MAX_MESSAGE_SIZE];
    source_t *source;
    token_t token;
    bot_chat_t *chat = NULL;
    bot_chattype_t *chattype = NULL;
    bot_chatmessage_t *chatmessage = NULL;
#ifdef DEBUG
    int starttime;

    starttime = Sys_MilliSeconds();
#endif //DEBUG
    //
    size = 0;
    foundchat = qfalse;
    //a bot chat is parsed in two phases
    for (pass = 0; pass < 2; pass++)
    {
        //allocate memory
        if (pass && size) ptr = (char *) GetClearedMemory(size);
        //load the source file
        PC_SetBaseFolder(BOTFILESBASEFOLDER);
        source = LoadSourceFile(chatfile);
        if (!source)
        {
            botimport.Print(PRT_ERROR, "counldn't load %s\n", chatfile);
            return NULL;
        } //end if
        //chat structure
        if (pass)
        {
            chat = (bot_chat_t *) ptr;
            ptr += sizeof(bot_chat_t);
        } //end if
        size = sizeof(bot_chat_t);
        //
        while(PC_ReadToken(source, &token))
        {
            if (!strcmp(token.string, "chat"))
            {
                if (!PC_ExpectTokenType(source, TT_STRING, 0, &token))
                {
                    FreeSource(source);
                    return NULL;
                } //end if
                StripDoubleQuotes(token.string);
                //after the chat name we expect a opening brace
                if (!PC_ExpectTokenString(source, "{"))
                {
                    FreeSource(source);
                    return NULL;
                } //end if
                //if the chat name is found
                if (!Q_stricmp(token.string, chatname))
                {
                    foundchat = qtrue;
                    //read the chat types
                    while(1)
                    {
                        if (!PC_ExpectAnyToken(source, &token))
                        {
                            FreeSource(source);
                            return NULL;
                        } //end if
                        if (!strcmp(token.string, "}")) break;
                        if (strcmp(token.string, "type"))
                        {
                            SourceError(source, "expected type found %s\n", token.string);
                            FreeSource(source);
                            return NULL;
                        } //end if
                        //expect the chat type name
                        if (!PC_ExpectTokenType(source, TT_STRING, 0, &token) ||
                                !PC_ExpectTokenString(source, "{"))
                        {
                            FreeSource(source);
                            return NULL;
                        } //end if
                        StripDoubleQuotes(token.string);
                        if (pass)
                        {
                            chattype = (bot_chattype_t *) ptr;
                            strncpy(chattype->name, token.string, MAX_CHATTYPE_NAME);
                            chattype->firstchatmessage = NULL;
                            //add the chat type to the chat
                            chattype->next = chat->types;
                            chat->types = chattype;
                            //
                            ptr += sizeof(bot_chattype_t);
                        } //end if
                        size += sizeof(bot_chattype_t);
                        //read the chat messages
                        while(!PC_CheckTokenString(source, "}"))
                        {
                            if (!BotLoadChatMessage(source, chatmessagestring))
                            {
                                FreeSource(source);
                                return NULL;
                            } //end if
                            if (pass)
                            {
                                chatmessage = (bot_chatmessage_t *) ptr;
                                chatmessage->time = -2*CHATMESSAGE_RECENTTIME;
                                //put the chat message in the list
                                chatmessage->next = chattype->firstchatmessage;
                                chattype->firstchatmessage = chatmessage;
                                //store the chat message
                                ptr += sizeof(bot_chatmessage_t);
                                chatmessage->chatmessage = ptr;
                                strcpy(chatmessage->chatmessage, chatmessagestring);
                                ptr += strlen(chatmessagestring) + 1;
                                //the number of chat messages increased
                                chattype->numchatmessages++;
                            } //end if
                            size += sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1;
                        } //end if
                    } //end while
                } //end if
                else //skip the bot chat
                {
                    indent = 1;
                    while(indent)
                    {
                        if (!PC_ExpectAnyToken(source, &token))
                        {
                            FreeSource(source);
                            return NULL;
                        } //end if
                        if (!strcmp(token.string, "{")) indent++;
                        else if (!strcmp(token.string, "}")) indent--;
                    } //end while
                } //end else
            } //end if
            else
            {
                SourceError(source, "unknown definition %s\n", token.string);
                FreeSource(source);
                return NULL;
            } //end else
        } //end while
        //free the source
        FreeSource(source);
        //if the requested character is not found
        if (!foundchat)
        {
            botimport.Print(PRT_ERROR, "couldn't find chat %s in %s\n", chatname, chatfile);
            return NULL;
        } //end if
    } //end for
    //
    botimport.Print(PRT_MESSAGE, "loaded %s from %s\n", chatname, chatfile);
    //
    //BotDumpInitialChat(chat);
    if (bot_developer->integer)
    {
        BotCheckInitialChatIntegrety(chat);
    } //end if
#ifdef DEBUG
    botimport.Print(PRT_MESSAGE, "initial chats loaded in %d msec\n", Sys_MilliSeconds() - starttime);
#endif //DEBUG
    //character was read succesfully
    return chat;
} //end of the function BotLoadInitialChat
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotFreeChatFile(int chatstate)
{
    bot_chatstate_t *cs;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return;
    if (cs->chat) FreeMemory(cs->chat);
    cs->chat = NULL;
} //end of the function BotFreeChatFile
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BotLoadChatFile(int chatstate, char *chatfile, char *chatname)
{
    bot_chatstate_t *cs;
    int n, avail = 0;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return BLERR_CANNOTLOADICHAT;
    BotFreeChatFile(chatstate);

    if (!LibVarGetValue("bot_reloadcharacters"))
    {
        avail = -1;
        for( n = 0; n < MAX_CLIENTS; n++ ) {
            if( !ichatdata[n] ) {
                if( avail == -1 ) {
                    avail = n;
                }
                continue;
            }
            if( strcmp( chatfile, ichatdata[n]->filename ) != 0 ) {
                continue;
            }
            if( strcmp( chatname, ichatdata[n]->chatname ) != 0 ) {
                continue;
            }
            cs->chat = ichatdata[n]->chat;
            //		botimport.Print( PRT_MESSAGE, "retained %s from %s\n", chatname, chatfile );
            return BLERR_NOERROR;
        }

        if( avail == -1 ) {
            botimport.Print(PRT_FATAL, "ichatdata table full; couldn't load chat %s from %s\n", chatname, chatfile);
            return BLERR_CANNOTLOADICHAT;
        }
    }

    cs->chat = BotLoadInitialChat(chatfile, chatname);
    if (!cs->chat)
    {
        botimport.Print(PRT_FATAL, "couldn't load chat %s from %s\n", chatname, chatfile);
        return BLERR_CANNOTLOADICHAT;
    } //end if
    if (!LibVarGetValue("bot_reloadcharacters"))
    {
        ichatdata[avail] = (bot_ichatdata_t *)GetClearedMemory( sizeof(bot_ichatdata_t) );
        ichatdata[avail]->chat = cs->chat;
        Q_strncpyz( ichatdata[avail]->chatname, chatname, sizeof(ichatdata[avail]->chatname) );
        Q_strncpyz( ichatdata[avail]->filename, chatfile, sizeof(ichatdata[avail]->filename) );
    } //end if

    return BLERR_NOERROR;
} //end of the function BotLoadChatFile
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotExpandChatMessage(char *outmessage, char *message, unsigned long mcontext,
                         bot_match_t *match, unsigned long vcontext, int reply)
{
    int num, len, i, expansion;
    char *outputbuf, *ptr, *msgptr;
    char temp[MAX_MESSAGE_SIZE];

    expansion = qfalse;
    msgptr = message;
    outputbuf = outmessage;
    len = 0;
    //
    while(*msgptr)
    {
        if (*msgptr == ESCAPE_CHAR)
        {
            msgptr++;
            switch(*msgptr)
            {
            case 'v': //variable
                {
                    msgptr++;
                    num = 0;
                    while(*msgptr && *msgptr != ESCAPE_CHAR)
                    {
                        num = num * 10 + (*msgptr++) - '0';
                    } //end while
                    //step over the trailing escape char
                    if (*msgptr) msgptr++;
                    if (num > MAX_MATCHVARIABLES)
                    {
                        botimport.Print(PRT_ERROR, "BotConstructChat: message %s variable %d out of range\n", message, num);
                        return qfalse;
                    } //end if
                    if (match->variables[num].offset >= 0)
                    {
                        assert( match->variables[num].offset >= 0 ); // bk001204
                        ptr = &match->string[ (int) match->variables[num].offset];
                        for (i = 0; i < match->variables[num].length; i++)
                        {
                            temp[i] = ptr[i];
                        } //end for
                        temp[i] = 0;
                        //if it's a reply message
                        if (reply)
                        {
                            //replace the reply synonyms in the variables
                            BotReplaceReplySynonyms(temp, vcontext);
                        } //end if
                        else
                        {
                            //replace synonyms in the variable context
                            BotReplaceSynonyms(temp, vcontext);
                        } //end else
                        //
                        if (len + strlen(temp) >= MAX_MESSAGE_SIZE)
                        {
                            botimport.Print(PRT_ERROR, "BotConstructChat: message %s too long\n", message);
                            return qfalse;
                        } //end if
                        strcpy(&outputbuf[len], temp);
                        len += strlen(temp);
                    } //end if
                    break;
                } //end case
            case 'r': //random
                {
                    msgptr++;
                    for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++)
                    {
                        temp[i] = *msgptr++;
                    } //end while
                    temp[i] = '\0';
                    //step over the trailing escape char
                    if (*msgptr) msgptr++;
                    //find the random keyword
                    ptr = RandomString(temp);
                    if (!ptr)
                    {
                        botimport.Print(PRT_ERROR, "BotConstructChat: unknown random string %s\n", temp);
                        return qfalse;
                    } //end if
                    if (len + strlen(ptr) >= MAX_MESSAGE_SIZE)
                    {
                        botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message);
                        return qfalse;
                    } //end if
                    strcpy(&outputbuf[len], ptr);
                    len += strlen(ptr);
                    expansion = qtrue;
                    break;
                } //end case
            default:
                {
                    botimport.Print(PRT_FATAL, "BotConstructChat: message \"%s\" invalid escape char\n", message);
                    break;
                } //end default
            } //end switch
        } //end if
        else
        {
            outputbuf[len++] = *msgptr++;
            if (len >= MAX_MESSAGE_SIZE)
            {
                botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message);
                break;
            } //end if
        } //end else
    } //end while
    outputbuf[len] = '\0';
    //replace synonyms weighted in the message context
    BotReplaceWeightedSynonyms(outputbuf, mcontext);
    //return true if a random was expanded
    return expansion;
} //end of the function BotExpandChatMessage
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotConstructChatMessage(bot_chatstate_t *chatstate, char *message, unsigned long mcontext,
                             bot_match_t *match, unsigned long vcontext, int reply)
{
    int i;
    char srcmessage[MAX_MESSAGE_SIZE];

    strcpy(srcmessage, message);
    for (i = 0; i < 10; i++)
    {
        if (!BotExpandChatMessage(chatstate->chatmessage, srcmessage, mcontext, match, vcontext, reply))
        {
            break;
        } //end if
        strcpy(srcmessage, chatstate->chatmessage);
    } //end for
    if (i >= 10)
    {
        botimport.Print(PRT_WARNING, "too many expansions in chat message\n");
        botimport.Print(PRT_WARNING, "%s\n", chatstate->chatmessage);
    } //end if
} //end of the function BotConstructChatMessage
//===========================================================================
// randomly chooses one of the chat message of the given type
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
char *BotChooseInitialChatMessage(bot_chatstate_t *cs, char *type)
{
    int n, numchatmessages;
    float besttime;
    bot_chattype_t *t;
    bot_chatmessage_t *m, *bestchatmessage;
    bot_chat_t *chat;

    chat = cs->chat;
    for (t = chat->types; t; t = t->next)
    {
        if (!Q_stricmp(t->name, type))
        {
            numchatmessages = 0;
            for (m = t->firstchatmessage; m; m = m->next)
            {
                if (m->time > AAS_Time()) continue;
                numchatmessages++;
            } //end if
            //if all chat messages have been used recently
            if (numchatmessages <= 0)
            {
                besttime = 0;
                bestchatmessage = NULL;
                for (m = t->firstchatmessage; m; m = m->next)
                {
                    if (!besttime || m->time < besttime)
                    {
                        bestchatmessage = m;
                        besttime = m->time;
                    } //end if
                } //end for
                if (bestchatmessage) return bestchatmessage->chatmessage;
            } //end if
            else //choose a chat message randomly
            {
                n = (int)(random() * numchatmessages);
                for (m = t->firstchatmessage; m; m = m->next)
                {
                    if (m->time > AAS_Time()) continue;
                    if (--n < 0)
                    {
                        m->time = AAS_Time() + CHATMESSAGE_RECENTTIME;
                        return m->chatmessage;
                    } //end if
                } //end for
            } //end else
            return NULL;
        } //end if
    } //end for
    return NULL;
} //end of the function BotChooseInitialChatMessage
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BotNumInitialChats(int chatstate, char *type)
{
    bot_chatstate_t *cs;
    bot_chattype_t *t;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return 0;

    for (t = cs->chat->types; t; t = t->next)
    {
        if (!Q_stricmp(t->name, type))
        {
            if (LibVarGetValue("bot_testichat")) {
                botimport.Print(PRT_MESSAGE, "%s has %d chat lines\n", type, t->numchatmessages);
                botimport.Print(PRT_MESSAGE, "-------------------\n");
            }
            return t->numchatmessages;
        } //end if
    } //end for
    return 0;
} //end of the function BotNumInitialChats
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7)
{
    char *message;
    int index;
    bot_match_t match;
    bot_chatstate_t *cs;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return;
    //if no chat file is loaded
    if (!cs->chat) return;
    //choose a chat message randomly of the given type
    message = BotChooseInitialChatMessage(cs, type);
    //if there's no message of the given type
    if (!message)
    {
#ifdef DEBUG
        botimport.Print(PRT_MESSAGE, "no chat messages of type %s\n", type);
#endif //DEBUG
        return;
    } //end if
    //
    Com_Memset(&match, 0, sizeof(match));
    index = 0;
    if( var0 ) {
        strcat(match.string, var0);
        match.variables[0].offset = index;
        match.variables[0].length = strlen(var0);
        index += strlen(var0);
    }
    if( var1 ) {
        strcat(match.string, var1);
        match.variables[1].offset = index;
        match.variables[1].length = strlen(var1);
        index += strlen(var1);
    }
    if( var2 ) {
        strcat(match.string, var2);
        match.variables[2].offset = index;
        match.variables[2].length = strlen(var2);
        index += strlen(var2);
    }
    if( var3 ) {
        strcat(match.string, var3);
        match.variables[3].offset = index;
        match.variables[3].length = strlen(var3);
        index += strlen(var3);
    }
    if( var4 ) {
        strcat(match.string, var4);
        match.variables[4].offset = index;
        match.variables[4].length = strlen(var4);
        index += strlen(var4);
    }
    if( var5 ) {
        strcat(match.string, var5);
        match.variables[5].offset = index;
        match.variables[5].length = strlen(var5);
        index += strlen(var5);
    }
    if( var6 ) {
        strcat(match.string, var6);
        match.variables[6].offset = index;
        match.variables[6].length = strlen(var6);
        index += strlen(var6);
    }
    if( var7 ) {
        strcat(match.string, var7);
        match.variables[7].offset = index;
        match.variables[7].length = strlen(var7);
        index += strlen(var7);
    }
    //
    BotConstructChatMessage(cs, message, mcontext, &match, 0, qfalse);
} //end of the function BotInitialChat
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotPrintReplyChatKeys(bot_replychat_t *replychat)
{
    bot_replychatkey_t *key;
    bot_matchpiece_t *mp;

    botimport.Print(PRT_MESSAGE, "[");
    for (key = replychat->keys; key; key = key->next)
    {
        if (key->flags & RCKFL_AND) botimport.Print(PRT_MESSAGE, "&");
        else if (key->flags & RCKFL_NOT) botimport.Print(PRT_MESSAGE, "!");
        //
        if (key->flags & RCKFL_NAME) botimport.Print(PRT_MESSAGE, "name");
        else if (key->flags & RCKFL_GENDERFEMALE) botimport.Print(PRT_MESSAGE, "female");
        else if (key->flags & RCKFL_GENDERMALE) botimport.Print(PRT_MESSAGE, "male");
        else if (key->flags & RCKFL_GENDERLESS) botimport.Print(PRT_MESSAGE, "it");
        else if (key->flags & RCKFL_VARIABLES)
        {
            botimport.Print(PRT_MESSAGE, "(");
            for (mp = key->match; mp; mp = mp->next)
            {
                if (mp->type == MT_STRING) botimport.Print(PRT_MESSAGE, "\"%s\"", mp->firststring->string);
                else botimport.Print(PRT_MESSAGE, "%d", mp->variable);
                if (mp->next) botimport.Print(PRT_MESSAGE, ", ");
            } //end for
            botimport.Print(PRT_MESSAGE, ")");
        } //end if
        else if (key->flags & RCKFL_STRING)
        {
            botimport.Print(PRT_MESSAGE, "\"%s\"", key->string);
        } //end if
        if (key->next) botimport.Print(PRT_MESSAGE, ", ");
        else botimport.Print(PRT_MESSAGE, "] = %1.0f\n", replychat->priority);
    } //end for
    botimport.Print(PRT_MESSAGE, "{\n");
} //end of the function BotPrintReplyChatKeys
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7)
{
    bot_replychat_t *rchat, *bestrchat;
    bot_replychatkey_t *key;
    bot_chatmessage_t *m, *bestchatmessage;
    bot_match_t match, bestmatch;
    int bestpriority, num, found, res, numchatmessages, index;
    bot_chatstate_t *cs;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return qfalse;
    Com_Memset(&match, 0, sizeof(bot_match_t));
    strcpy(match.string, message);
    bestpriority = -1;
    bestchatmessage = NULL;
    bestrchat = NULL;
    //go through all the reply chats
    for (rchat = replychats; rchat; rchat = rchat->next)
    {
        found = qfalse;
        for (key = rchat->keys; key; key = key->next)
        {
            res = qfalse;
            //get the match result
            if (key->flags & RCKFL_NAME) res = (StringContains(message, cs->name, qfalse) != -1);
            else if (key->flags & RCKFL_BOTNAMES) res = (StringContains(key->string, cs->name, qfalse) != -1);
            else if (key->flags & RCKFL_GENDERFEMALE) res = (cs->gender == CHAT_GENDERFEMALE);
            else if (key->flags & RCKFL_GENDERMALE) res = (cs->gender == CHAT_GENDERMALE);
            else if (key->flags & RCKFL_GENDERLESS) res = (cs->gender == CHAT_GENDERLESS);
            else if (key->flags & RCKFL_VARIABLES) res = StringsMatch(key->match, &match);
            else if (key->flags & RCKFL_STRING) res = (StringContainsWord(message, key->string, qfalse) != NULL);
            //if the key must be present
            if (key->flags & RCKFL_AND)
            {
                if (!res)
                {
                    found = qfalse;
                    break;
                } //end if
            } //end else if
            //if the key must be absent
            else if (key->flags & RCKFL_NOT)
            {
                if (res)
                {
                    found = qfalse;
                    break;
                } //end if
            } //end if
            else if (res)
            {
                found = qtrue;
            } //end else
        } //end for
        //
        if (found)
        {
            if (rchat->priority > bestpriority)
            {
                numchatmessages = 0;
                for (m = rchat->firstchatmessage; m; m = m->next)
                {
                    if (m->time > AAS_Time()) continue;
                    numchatmessages++;
                } //end if
                num = (int)(random() * numchatmessages);
                for (m = rchat->firstchatmessage; m; m = m->next)
                {
                    if (--num < 0) break;
                    if (m->time > AAS_Time()) continue;
                } //end for
                //if the reply chat has a message
                if (m)
                {
                    Com_Memcpy(&bestmatch, &match, sizeof(bot_match_t));
                    bestchatmessage = m;
                    bestrchat = rchat;
                    bestpriority = (int)rchat->priority;
                } //end if
            } //end if
        } //end if
    } //end for
    if (bestchatmessage)
    {
        index = strlen(bestmatch.string);
        if( var0 ) {
            strcat(bestmatch.string, var0);
            bestmatch.variables[0].offset = index;
            bestmatch.variables[0].length = strlen(var0);
            index += strlen(var0);
        }
        if( var1 ) {
            strcat(bestmatch.string, var1);
            bestmatch.variables[1].offset = index;
            bestmatch.variables[1].length = strlen(var1);
            index += strlen(var1);
        }
        if( var2 ) {
            strcat(bestmatch.string, var2);
            bestmatch.variables[2].offset = index;
            bestmatch.variables[2].length = strlen(var2);
            index += strlen(var2);
        }
        if( var3 ) {
            strcat(bestmatch.string, var3);
            bestmatch.variables[3].offset = index;
            bestmatch.variables[3].length = strlen(var3);
            index += strlen(var3);
        }
        if( var4 ) {
            strcat(bestmatch.string, var4);
            bestmatch.variables[4].offset = index;
            bestmatch.variables[4].length = strlen(var4);
            index += strlen(var4);
        }
        if( var5 ) {
            strcat(bestmatch.string, var5);
            bestmatch.variables[5].offset = index;
            bestmatch.variables[5].length = strlen(var5);
            index += strlen(var5);
        }
        if( var6 ) {
            strcat(bestmatch.string, var6);
            bestmatch.variables[6].offset = index;
            bestmatch.variables[6].length = strlen(var6);
            index += strlen(var6);
        }
        if( var7 ) {
            strcat(bestmatch.string, var7);
            bestmatch.variables[7].offset = index;
            bestmatch.variables[7].length = strlen(var7);
            index += strlen(var7);
        }
        if (LibVarGetValue("bot_testrchat"))
        {
            for (m = bestrchat->firstchatmessage; m; m = m->next)
            {
                BotConstructChatMessage(cs, m->chatmessage, mcontext, &bestmatch, vcontext, qtrue);
                BotRemoveTildes(cs->chatmessage);
                botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage);
            } //end if
        } //end if
        else
        {
            bestchatmessage->time = AAS_Time() + CHATMESSAGE_RECENTTIME;
            BotConstructChatMessage(cs, bestchatmessage->chatmessage, mcontext, &bestmatch, vcontext, qtrue);
        } //end else
        return qtrue;
    } //end if
    return qfalse;
} //end of the function BotReplyChat
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BotChatLength(int chatstate)
{
    bot_chatstate_t *cs;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return 0;
    return strlen(cs->chatmessage);
} //end of the function BotChatLength
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotEnterChat(int chatstate, int clientto, int sendto)
{
    bot_chatstate_t *cs;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return;

    if (strlen(cs->chatmessage))
    {
        BotRemoveTildes(cs->chatmessage);
        if (LibVarGetValue("bot_testichat")) {
            botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage);
        }
        else {
            switch(sendto) {
            case CHAT_TEAM:
                EA_Command(cs->client, va("say_team %s", cs->chatmessage));
                break;
            case CHAT_TELL:
                EA_Command(cs->client, va("tell %d %s", clientto, cs->chatmessage));
                break;
            default: //CHAT_ALL
                EA_Command(cs->client, va("say %s", cs->chatmessage));
                break;
            }
        }
        //clear the chat message from the state
        strcpy(cs->chatmessage, "");
    } //end if
} //end of the function BotEnterChat
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotGetChatMessage(int chatstate, char *buf, int size)
{
    bot_chatstate_t *cs;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return;

    BotRemoveTildes(cs->chatmessage);
    strncpy(buf, cs->chatmessage, size-1);
    buf[size-1] = '\0';
    //clear the chat message from the state
    strcpy(cs->chatmessage, "");
} //end of the function BotGetChatMessage
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotSetChatGender(int chatstate, int gender)
{
    bot_chatstate_t *cs;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return;
    switch(gender)
    {
    case CHAT_GENDERFEMALE: cs->gender = CHAT_GENDERFEMALE; break;
    case CHAT_GENDERMALE: cs->gender = CHAT_GENDERMALE; break;
    default: cs->gender = CHAT_GENDERLESS; break;
    } //end switch
} //end of the function BotSetChatGender
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotSetChatName(int chatstate, char *name, int client)
{
    bot_chatstate_t *cs;

    cs = BotChatStateFromHandle(chatstate);
    if (!cs) return;
    cs->client = client;
    Com_Memset(cs->name, 0, sizeof(cs->name));
    strncpy(cs->name, name, sizeof(cs->name));
    cs->name[sizeof(cs->name)-1] = '\0';
} //end of the function BotSetChatName
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotResetChatAI(void)
{
    bot_replychat_t *rchat;
    bot_chatmessage_t *m;

    for (rchat = replychats; rchat; rchat = rchat->next)
    {
        for (m = rchat->firstchatmessage; m; m = m->next)
        {
            m->time = 0;
        } //end for
    } //end for
} //end of the function BotResetChatAI
//========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//========================================================================
int BotAllocChatState(void)
{
    int i;

    for (i = 1; i <= MAX_CLIENTS; i++)
    {
        if (!botchatstates[i])
        {
            botchatstates[i] = (bot_chatstate_t *)GetClearedMemory(sizeof(bot_chatstate_t));
            return i;
        }
    }
    return 0;
} //end of the function BotAllocChatState
//========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//========================================================================
void BotFreeChatState(int handle)
{
    bot_chatstate_t *cs;
    bot_consolemessage_t m;
    int h;

    if (handle <= 0 || handle > MAX_CLIENTS)
    {
        botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle);
        return;
    } //end if
    if (!botchatstates[handle])
    {
        botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle);
        return;
    } //end if
    cs = botchatstates[handle];
    if (LibVarGetValue("bot_reloadcharacters"))
    {
        BotFreeChatFile(handle);
    } //end if
    //free all the console messages left in the chat state
    for (h = BotNextConsoleMessage(handle, &m); h; h = BotNextConsoleMessage(handle, &m))
    {
        //remove the console message
        BotRemoveConsoleMessage(handle, h);
    } //end for
    FreeMemory(botchatstates[handle]);
    botchatstates[handle] = NULL;
} //end of the function BotFreeChatState
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BotSetupChatAI(void)
{
    char *file;

#ifdef DEBUG
    int starttime = Sys_MilliSeconds();
#endif //DEBUG

    file = LibVarString("synfile", "syn.c");
    synonyms = BotLoadSynonyms(file);
    file = LibVarString("rndfile", "rnd.c");
    randomstrings = BotLoadRandomStrings(file);
    file = LibVarString("matchfile", "match.c");
    matchtemplates = BotLoadMatchTemplates(file);
    //
    if (!LibVarValue("nochat", "0"))
    {
        file = LibVarString("rchatfile", "rchat.c");
        replychats = BotLoadReplyChat(file);
    } //end if

    InitConsoleMessageHeap();

#ifdef DEBUG
    botimport.Print(PRT_MESSAGE, "setup chat AI %d msec\n", Sys_MilliSeconds() - starttime);
#endif //DEBUG
    return BLERR_NOERROR;
} //end of the function BotSetupChatAI
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotShutdownChatAI(void)
{
    int i;

    //free all remaining chat states
    for(i = 0; i < MAX_CLIENTS; i++)
    {
        if (botchatstates[i])
        {
            BotFreeChatState(i);
        } //end if
    } //end for
    //free all cached chats
    for(i = 0; i < MAX_CLIENTS; i++)
    {
        if (ichatdata[i])
        {
            FreeMemory(ichatdata[i]->chat);
            FreeMemory(ichatdata[i]);
            ichatdata[i] = NULL;
        } //end if
    } //end for
    if (consolemessageheap) FreeMemory(consolemessageheap);
    consolemessageheap = NULL;
    if (matchtemplates) BotFreeMatchTemplates(matchtemplates);
    matchtemplates = NULL;
    if (randomstrings) FreeMemory(randomstrings);
    randomstrings = NULL;
    if (synonyms) FreeMemory(synonyms);
    synonyms = NULL;
    if (replychats) BotFreeReplyChat(replychats);
    replychats = NULL;
} //end of the function BotShutdownChatAI
